/*
 * =====================================================================================
 *
 *       Filename:  socket_connect.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2025年05月12日 14时38分36秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  xiaojian jiang (Adams), <yunsangc@163.com>
 *   Organization:  
 *
 * =====================================================================================
 */
#include <stdio.h>
#include <stdint.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

#include "log.h"
#include "list.h"
#include "config.h"
#include "common.h"
#include "socket_connect.h"

struct ev_loop *loop;
client_ctx_t client_ctx = {.fd = -1, .connect_stat = NO_CONNECT};

client_ctx_t *get_client_ctx()
{
	return &client_ctx;
}

/* TCP 设定 keep alive 开启并设置参数 */
int tcp_set_keep_alive(int sock, int idle, int itv, int cnt)
{
	int optval = 1;
	int optlen = sizeof(optval);
	if(setsockopt(sock, SOL_SOCKET,	SO_KEEPALIVE, &optval, optlen) < -1) {
		sys_debug("Set tcp keepalive failed: %s\n", strerror(errno));
		Lzlog_error("Set tcp keepalive failed: %s", strerror(errno));
		return -1;
	}
	optval = cnt;
	if(setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < -1) {
		sys_debug("Set tcp_keepalive_probes failed: %s\n", strerror(errno));
		Lzlog_error("Set tcp_keepalive_probes failed: %s", strerror(errno));
		return -1;
	}
	optval = idle;
	if(setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < -1) {
		sys_debug("Set tcp_keepalive_time failed: %s", strerror(errno));
		Lzlog_error("Set tcp_keepalive_time failed: %s\n", strerror(errno));
		return -1;
	}
	optval = itv;
	if(setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < -1) {
		sys_debug("Set tcp_keepalive_intvl failed: %s", strerror(errno));
		Lzlog_error("Set tcp_keepalive_intvl failed: %s\n", strerror(errno));
		return -1;
	}
	return 1;
}

/* 设置非阻塞模式 */
int set_nonblocking(int sockfd)
{
	int flags = fcntl(sockfd, F_GETFL, 0);
	if (flags == -1) {
		Lzlog_error("fcntl get flags failed");
		return -1;
	}

	if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
		Lzlog_error("fcntl set flags failed");
		return -1;
	}
	return 0;
}

/* 恢复为阻塞模式 */
int set_blocking(int sockfd)
{
	int flags = fcntl(sockfd, F_GETFL, 0);
	if (flags == -1) {
		Lzlog_error("fcntl get flags failed");
		return -1;
	}

	if (fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
		Lzlog_error("fcntl set flags failed");
		return -1;
	}
	return 0;
}

/* 创建 TCP socket 并设置 keepalive */
int creat_TCP_socket()
{
	int fd = -1;
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd == -1) {
		sys_err("Creat TCP socket failed!(%d[%s])\n", errno, strerror(errno));
		Lzlog_error("Creat TCP socket failed!(%d[%s])", errno, strerror(errno));
	}
	int optval = 1;
	int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
	if (ret < 0) {
		sys_err("setsockopt SO_REUSEADDR failed!(%d[%s])\n", errno, strerror(errno));
		Lzlog_error("setsockopt SO_REUSEADDR failed!(%d[%s])", errno, strerror(errno));
		close(fd);
		fd = -1;
	}
	return fd;
}

/* 配合libev 非阻塞 connect */
int connect_to_server_nonblock(int sockfd, const char *ip, int port)
{
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);

	if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) {
		sys_err("invalid address/ address not supported.\n");
		Lzlog_error("invalid address/ address not supported.");
		return -1;
	}

	set_nonblocking(sockfd);
	int ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (ret < 0 && errno != EINPROGRESS) {
		sys_err("connection failed.\n");
		Lzlog_error("connection failed.");
		return -1;
	}
	return 0;
}

/* 结束 TCP 连接 */
void client_ctx_stop()
{
	if (client_ctx.connect_stat == NO_CONNECT) { //未连接
		return;
	}

	client_ctx.connect_stat = NO_CONNECT;
	ev_io_stop(loop, &client_ctx.recv_io);
	memset(client_ctx.ipaddr, 0, 16);
	close(client_ctx.fd);
	client_ctx.fd = -1;
	client_ctx.count = 0;
	client_ctx.size = 0;
}

/* 通过 TCP 发送数据 */
int send_data_by_tcp(const unsigned char* pkg, size_t pkg_len)
{
	if (client_ctx.connect_stat != CONNECTED) {
		return -1;
	}
	ssize_t ret = send(client_ctx.fd, pkg, pkg_len, 0);
	if (ret < 0) {
		if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
			sys_err("send failed! stop it.\n");
			client_ctx_stop();
		}
	}
	client_ctx.count++;
	client_ctx.size += ret;
	sys_debug("Send Failed!:%d\n", errno);
	return ret;
}

/* socket rio call back function */
void recv_cb(struct ev_loop *loop, struct ev_io *w, int revents)
{
	int fd = w->fd;
	char buffer[128] = {0};
	ssize_t recv_len = recv(fd, buffer, 128, 0);
	if (!recv_len) {
		sys_debug("TCP Socket server closed!, return.\n");
		client_ctx_stop();
		return;
	} else if (recv_len == -1) {
		return;
	}
	dump_hex_c(buffer, recv_len, 16);
}

/* socket connect io call back function */
void connect_cb(struct ev_loop *loop, struct ev_io *w, int revents)
{
	int fd = w->fd;
	int error;
	socklen_t len = sizeof(error);
	ev_io_stop(loop, &client_ctx.connect_io);

	int ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len);
	if (ret < 0) {
		sys_debug("getsockopt failed[fd:%d]\n", fd);
		Lzlog_error("getsockopt failed");
		client_ctx.connect_stat = NO_CONNECT;
		close(fd);
		return;
	}
	if (error != 0) {
		Lzlog_error("connection error: %s", strerror(error));
		sys_debug("connection failed.\n");
		client_ctx.connect_stat = NO_CONNECT;
		close(fd);
		return;
	}
	sys_debug("Connect to %s:%d Success,FD:%d.\n", client_ctx.ipaddr, client_ctx.port, fd);

	ev_io_init(&client_ctx.recv_io, recv_cb, fd, EV_READ);
	ev_io_start(loop, &client_ctx.recv_io);

	client_ctx.connect_stat = CONNECTED;
}
/* TCP socket init */
int socket_client_init(char *ipaddr, int port)
{
	if (!strcmp(client_ctx.ipaddr, ipaddr) && client_ctx.port == port) {
		if (client_ctx.connect_stat == CONNECTED) {
			sys_debug("Same IP and port:[%s:%d], [%s:%d], and is connected.\n", ipaddr, port,  client_ctx.ipaddr, client_ctx.port);
			return -2;
		}
	}
	/* 
	 * 以下状态都需要清理以下
	 * 1. 新连接的ip 或者 port 与当前不一样
	 * 2. 相同的IP和port但是状态不是空闲未连接状态
	 */
	if (client_ctx.connect_stat != NO_CONNECT) {
		client_ctx_stop();
	}

	client_ctx.fd = creat_TCP_socket();
	if (client_ctx.fd < -1) {
		return -1;
	}

	strcpy(client_ctx.ipaddr, ipaddr);
	client_ctx.port = port;

	ev_io_init(&client_ctx.connect_io, connect_cb, client_ctx.fd, EV_WRITE);
	ev_io_start(loop, &client_ctx.connect_io);

	client_ctx.connect_stat = CONNECTING;

	int ret = connect_to_server_nonblock(client_ctx.fd, ipaddr, port);
	if (ret < 0) {
		ev_io_stop(loop, &client_ctx.connect_io);
		return -1;
	}

	return 0;
}
